/*
* Copyright (c) 2007 by Kirill Kolodyazhniy.
* See the file "license.terms" for information on usage and redistribution.
*/

#include "filesystem.h"
#include "log.h"

#include "sys/types.h"
#include "sys/stat.h"
#include <io.h>

#ifdef _DEBUG
#ifdef _MSC_VER
   #define new new( _CLIENT_BLOCK, __FILE__, __LINE__)
#endif
#endif // _DEBUG

namespace Engine
{
	using namespace std;
	/**********************************************************************************************************/
	FileSystem* FileSystemFactory::CreateInstance( const std::string& name )
	{
		return new FileSystem(name);
	}
	/**********************************************************************************************************/
	FileSystem::FileSystem(const std::string& name):
		m_name(name)
	{
	}
	/**********************************************************************************************************/
	Archive::VectorStringPtr FileSystem::Find(const std::string& pattern, bool recursive, bool dirs)const
	{
		Archive::VectorStringPtr simpleList(new vector<string>);
		FindFiles(pattern,recursive,dirs,simpleList);
		return simpleList;
	}
	/**********************************************************************************************************/
	static bool is_reserved_dir (const char *fn)
    {
        return (fn [0] == '.' && (fn [1] == 0 || (fn [1] == '.' && fn [2] == 0)));
    }
    /**********************************************************************************************************/
    static bool is_absolute_path(const char* path)
    {
#ifdef _WIN32
        if (isalpha(static_cast<unsigned char>(path[0])) && path[1] == ':')
            return true;
		return false;
#else
        return path[0] == '/' || path[0] == '\\';
#endif
    }
    /**********************************************************************************************************/
    static string concatenate_path(const string& base, const string& name)
    {
        if (base.empty() || is_absolute_path(name.c_str()))
            return name;
        else
            return base + '/' + name;
    }
	/**********************************************************************************************************/
	void FileSystem::FindFiles(const std::string& pattern, bool recursive, bool dirs, Archive::VectorStringPtr simpleList)const
	{
		long lHandle, res;
		struct _finddata_t tagData;


		// pattern can contain a directory name, separate it from mask
		size_t pos1 = pattern.rfind ('/');
		size_t pos2 = pattern.rfind ('\\');
		if (pos1 == pattern.npos || ((pos2 != pattern.npos) && (pos1 < pos2)))
			pos1 = pos2;
		string directory;
		if (pos1 != pattern.npos)
			directory = pattern.substr (0, pos1 + 1);

		string full_pattern = concatenate_path(m_name, pattern);

		lHandle = _findfirst(full_pattern.c_str(), &tagData);
		res = 0;
		while (lHandle != -1 && res != -1)
		{
			if ((dirs == ((tagData.attrib & _A_SUBDIR) != 0)) && (!dirs || !is_reserved_dir (tagData.name)))
			{
				simpleList->push_back(directory + tagData.name);
			}
			res = _findnext( lHandle, &tagData );
		}
		// Close if we found any files
		if(lHandle != -1)
			_findclose(lHandle);

		// Now find directories
		if (recursive)
		{
			string base_dir = m_name;
			if (!directory.empty ())
			{
				base_dir = concatenate_path(m_name, directory);
				// Remove the last '/'
				base_dir.erase (base_dir.length () - 1);
			}
			base_dir.append ("/*");

			// Remove directory name from pattern
			string mask ("/");
			if (pos1 != pattern.npos)
				mask.append (pattern.substr (pos1 + 1));
			else
				mask.append (pattern);

			lHandle = _findfirst(base_dir.c_str (), &tagData);
			res = 0;
			while (lHandle != -1 && res != -1)
			{
				if ((tagData.attrib & _A_SUBDIR) &&	!is_reserved_dir (tagData.name))
				{
					// recurse
					base_dir = directory;
					base_dir.append (tagData.name).append (mask);
					FindFiles(base_dir, recursive, dirs, simpleList);
				}
				res = _findnext( lHandle, &tagData );
			}
			// Close if we found any files
			if(lHandle != -1)
				_findclose(lHandle);
		}
	}
	/**********************************************************************************************************/
	RefPtr<DataStream> FileSystem::ReadFile(const std::string& name) const
	{
		string fpath = concatenate_path(m_name,name);

		struct stat st;
		memset(&st,0,sizeof(st));
		int ret = stat(fpath.c_str(),&st);
		if( ret != 0 )
		{
			Log::GetSingleton() << "ERROR: Can't get size for file \"" << fpath << "\"" << endl;
			return RefPtr<DataStream>();
		}


		RefPtr<ifstream> file(new ifstream(fpath.c_str(),ios_base::in|ios_base::binary));
		if((*file.GetPointer()))
		{
			return RefPtr<DataStream>(new FileSystemDataStream(file,st.st_size));
		}
		Log::GetSingleton() << "ERROR: Can't open file \"" << fpath << "\"" << endl;
		return RefPtr<DataStream>();
	}
	/**********************************************************************************************************/


	/**********************************************************************************************************/
	FileSystemDataStream::FileSystemDataStream(RefPtr<std::ifstream> s,size_t size):
		m_file(s),
		m_size(size)
	{
		if(m_file.IsNull() || m_size < 0)
		{
			Log::GetSingleton()<< "ERROR: FileSystemDataStream bad file stream in constructor." << endl;
			throw std::invalid_argument("FileSystemDataStream bad file stream in constructor.");
		}
	}
	/**********************************************************************************************************/
	FileSystemDataStream::~FileSystemDataStream()
	{
		Close();
	}
	/**********************************************************************************************************/
	size_t FileSystemDataStream::Read(void* buf, size_t count)
	{
		m_file->read(static_cast<char*>(buf), static_cast<std::streamsize>(count));
        return m_file->gcount();
	}
	/**********************************************************************************************************/
	void FileSystemDataStream::Seek( size_t pos )
	{
		m_file->clear(); //Clear fail status in case eof was set
        m_file->seekg(static_cast<std::streamoff>(pos), std::ios::beg);
	}
	/**********************************************************************************************************/
	size_t FileSystemDataStream::Tell(void) const
	{
		m_file->clear(); //Clear fail status in case eof was set
		return m_file->tellg();
	}
	/**********************************************************************************************************/
	bool FileSystemDataStream::Eof(void) const
	{
		return m_file->eof();
	}
	/**********************************************************************************************************/
	void FileSystemDataStream::Close(void)
	{
		m_file->close();
	}
	/**********************************************************************************************************/
	size_t FileSystemDataStream::GetSize(void)
	{
		return m_size;
	}
	/**********************************************************************************************************/
}
